/*
 * Copyright 2010 - Jeremie Brebec
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.jb.sharedbills.shared.model.logic;

import java.util.Collection;

import org.jb.sharedbills.shared.model.Bill;
import org.jb.sharedbills.shared.model.Event;
import org.jb.sharedbills.shared.model.Group;
import org.jb.sharedbills.shared.model.Member;
import org.jb.sharedbills.shared.model.notification.CollectionNotification;
import org.jb.sharedbills.shared.model.notification.Notification;
import org.jb.sharedbills.shared.model.notification.NotificationListener;
import org.jb.sharedbills.shared.model.notification.PropertyChangeNotification;

/**
 * 
 * @author jeremie
 *
 */
public class BillCalculator {

	/*
	 * 
	 */
	public BillCalculator( final Event event ) {
		this.event = event;
		event.getBills().addListener( new NotificationListener() {
			
			@Override
			public void handle(Notification notification) {
				if (notification.getSender() == event ) {
					
					if (notification.getProperty().equals("bills")) {
						handleBillsNotification( notification );
					}
				}
				else if (notification.getSender() instanceof Bill) {
					handleBillNotification( (Bill)notification.getSender(), (PropertyChangeNotification<?>)notification );
				}
			}
		});
		event.getMembers().addListener(new NotificationListener() {
			
			@Override
			public void handle(Notification notification) {
				if (notification.getSender() == event ) {
					if (notification.getProperty().equals("members")) {
						handleMembersNotification( notification );
					}
				}
			}
		});
		event.getGroups().addListener(new NotificationListener() {
			
			@Override
			public void handle(Notification notification) {
				if (notification.getSender() instanceof Group) {
					handleGroupNotification( (Group)notification.getSender(), notification );
				}
			}
		});
	}
	
	/**
	 * 
	 * @param group
	 * @param notification
	 */
	@SuppressWarnings("unchecked")
	private void handleGroupNotification( Group group, Notification notification ) {
		if (notification.getProperty().equals("members")) {
			if (notification instanceof CollectionNotification<?>) {
				CollectionNotification<Member> memberNotif = (CollectionNotification<Member>) notification;
				
				//
				// Parcours des dépenses impactées
				//
				for( Bill bill : event.getBills() ) {
					if (bill.getTo() == group) {
						
						double oldUsedByMembers = bill.getAmount() / (group.getMembers().size() + memberNotif.getRemoved().size() - memberNotif.getAdded().size() );
						double newUserByMembers = bill.getAmount() / group.getMembers().size();
						
						for( Member b : memberNotif.getRemoved() ) {
							b.setUsed( b.getUsed() - oldUsedByMembers );
						}
						
						for( Member b : group.getMembers() ) {
							if (!memberNotif.getAdded().contains(b)) {
								//
								// Ancien membre : on rembourse et on redebite
								//
								b.setUsed( b.getUsed() - oldUsedByMembers + newUserByMembers );
							}
							else {
								//
								// Nouveau membre : ajout de la somme due
								//
								b.setUsed( b.getUsed() + newUserByMembers );
							}
						}
					}
				}
			}
		}
	}
	
	/*
	 * 
	 */
	private void handleBillNotification(Bill bill, PropertyChangeNotification<?> notification) {

		/*
		 * 
		 */
		if (notification.getProperty().equals("from")) {
			
			Member oldMember = (Member)notification.getOldValue();
			Member newMember = (Member)notification.getNewValue();
			
			if (oldMember != null) {
				oldMember.setMaxAmount( oldMember.getMaxAmount() - bill.getAmount() );
			}
			if (newMember != null) {
				newMember.setMaxAmount( newMember.getMaxAmount() + bill.getAmount() );
			}
			
		}
		else if (notification.getProperty().equals("to")) {
			
			Group oldTo = (Group)notification.getOldValue();
			Group newTo = (Group)notification.getNewValue();
			
			if (oldTo != null && !oldTo.getMembers().isEmpty()) {
				
				double usedByMembers = bill.getAmount() / oldTo.getMembers().size();

				for( Member member : oldTo.getMembers() ) {
					member.setUsed( member.getUsed() - member.getCount() * usedByMembers );
				}
			}
			
			if (newTo != null && !newTo.getMembers().isEmpty()) {
				double usedByMembers = bill.getAmount() / newTo.getMembers().size();

				for( Member member : newTo.getMembers() ) {
					member.setUsed( member.getUsed() + member.getCount() * usedByMembers );
				}
				
			}
		}
		else if (notification.getProperty().equals("amount")) {
			
			if (bill.getTo() == null || bill.getTo().getMembers().isEmpty())
				return;
			
			if (bill.getFrom() == null)
				return;
			
			double oldAmount = (Double) notification.getOldValue();
			double newAmount = (Double) notification.getNewValue();
			
			bill.getFrom().setMaxAmount( bill.getFrom().getMaxAmount() - oldAmount + newAmount );
			
			double usedByMembers = (newAmount - oldAmount) / bill.getTo().getMembers().size();

			/*
			 * 
			 */
			for( Member member : bill.getTo().getMembers() ) {
				member.setUsed( member.getUsed() + member.getCount() * usedByMembers );
			}
			
		}
	}

	/*
	 * 
	 */
	@SuppressWarnings("unchecked")
	private void handleBillsNotification(Notification notification) {
		if (notification instanceof CollectionNotification<?>) {
			handleNewBills( ((CollectionNotification<Bill>)notification).getAdded() ); 
			handleOldBills( ((CollectionNotification<Bill>)notification).getRemoved() ); 
		}
	}

	/*
	 * 
	 */
	private void handleNewBills(Collection<? extends Bill> bills) {
		for( Bill bill : bills ) {
			
			if (bill.getFrom() == null || bill.getTo() == null)
				continue;
			
			if (bill.getTo().getMembers().isEmpty())
				continue;
			
			bill.getFrom().setMaxAmount( bill.getFrom().getMaxAmount() + bill.getAmount() );
			
			double usedByMembers = bill.getAmount() / bill.getTo().getMembers().size();
			
			/*
			 * 
			 */
			for( Member member : bill.getTo().getMembers() ) {
				member.setUsed( member.getUsed() + member.getCount() * usedByMembers );
			}
		}
	}
	
	/**
	 * 
	 * @param notification
	 */
	@SuppressWarnings("unchecked")
	private void handleMembersNotification( Notification notification ) {
		if (notification instanceof CollectionNotification<?>) {
			CollectionNotification<Member> memberNotif = (CollectionNotification<Member>)notification;
			
			Group allMembersGroup = event.getAllMembersGroup();
			allMembersGroup.getMembers().addAll( memberNotif.getAdded() );
			
			if (!memberNotif.getRemoved().isEmpty()) {
				for( Group g : event.getGroups() ) {
					g.getMembers().removeAll( memberNotif.getRemoved() );
				}
			}
		}
	}
	
	/*
	 * 
	 */
	private void handleOldBills(Collection<? extends Bill> bills) {
		for( Bill bill : bills ) {
			
			if (bill.getFrom() == null || bill.getTo() == null)
				continue;
			
			if (bill.getTo().getMembers().isEmpty())
				continue;
			
			bill.getFrom().setMaxAmount( bill.getFrom().getMaxAmount() - bill.getAmount() );
			
			double usedByMembers = bill.getAmount() / bill.getTo().getMembers().size();
			
			/*
			 * 
			 */
			for( Member member : bill.getTo().getMembers() ) {
				member.setUsed( member.getUsed() - member.getCount() * usedByMembers );
			}
		}	
	}

	private final Event event;
}
